/*
 * Copyright 2009 XueSong Guo.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.webwheel.di.engine;

import cn.webwheel.di.*;

import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * WebWheel DI容器的默认实现类。<br>
 */
@SuppressWarnings("unchecked")
public class DefaultContainer implements Container {

    final Map<String, Class<? extends Annotation>> namedDesireMap = new ConcurrentHashMap<String, Class<? extends Annotation>>();

    private final Map<Key, Provider> map = new ConcurrentHashMap<Key, Provider>();

    private final NativeProvider nativeProvider;
    final DefaultContainer[] parents;

    private final static Object Null = new Object();

    /**
     * 可传入若干父级DI容器，当子容器中查找不到类型绑定时，则会从父级查找。
     * @param parents 父级容器
     */
    public DefaultContainer(DefaultContainer... parents) {
        this.parents = parents;
        nativeProvider = new NativeProviderImpl(this);
    }

    public <T> void bind(Key<T> key, Provider<? extends T> provider) {
        if(provider==null) map.remove(key);
        else map.put(key, provider);
    }

    /**
     * 映射一个命名注入需求到一个注释接口类型<br>
     * 此注释类型必须拥有一个字符串类型的value属性，为动态注入参数。<br>
     * 映射之后，使用这个注释就相当于使用了对应注入静态参数的注入需求接口。
     * @param name 静态参数
     * @param anCls 注释接口
     * @throws IllegalArgumentException 注释类型不满足要求
     */
    public void addNamedDesire(String name, Class<? extends Annotation> anCls) {
        if (anCls == null) {
            namedDesireMap.put(name, null);
        } else {
            Retention ret = anCls.getAnnotation(Retention.class);
            if (ret == null) {
                throw new IllegalArgumentException("no Retention annotation at " + anCls);
            }
            if (ret.value() != RetentionPolicy.RUNTIME) {
                throw new IllegalArgumentException("Retention is not RUNTIME at " + anCls);
            }
            try {
                Method method = anCls.getMethod("value");
                if (method.getReturnType() != String.class) {
                    throw new IllegalArgumentException("value() method should return String type in " + anCls);
                }
            } catch (NoSuchMethodException e) {
                throw new IllegalArgumentException("can not find value() method in " + anCls);
            }
            namedDesireMap.put(name, anCls);
        }
    }

    /**
     * 获取通过{@link #addNamedDesire(String, Class)} 方法设置的注释接口类型
     * @param name 静态参数
     * @return 注释接口类型
     */
    public Class<? extends Annotation> getNamedDesire(String name) {
        return namedDesireMap.get(name);
    }

    Map<String, Class<? extends Annotation>> getAllNamedDesire() {
        Map<String, Class<? extends Annotation>> map = new HashMap<String, Class<? extends Annotation>>();
        for (DefaultContainer parent : parents) {
            map.putAll(parent.getAllNamedDesire());
        }
        map.putAll(namedDesireMap);
        return map;
    }

    private <T> T getInstance0(Key<T> key, String data) {
        Provider<T> provider = map.get(key);
        if (provider != null) {
            return provider.get(key, data, nativeProvider);
        }
        return (T)Null;
    }

    public <T> T getInstance(Key<T> key, String data) {
        T obj = getInstance0(key, data);
        if(obj!=Null) return obj;
        for (DefaultContainer parent : parents) {
            obj = parent.getInstance0(key, data);
            if(obj!=Null) return obj;
        }
        return (T) nativeProvider.get(key);
    }

    public <T> Provider<? extends T> getProvider(Key<T> key) {
        Provider provider = map.get(key);
        if(provider!=null) return provider;
        for (DefaultContainer parent : parents) {
            provider = parent.getProvider(key);
            if(provider!=null) return provider;
        }
        return null;
    }

    public <T> Fetcher<T> createFetcher(final Key<T> key, final String data) {
        return new Fetcher<T>() {
            public T fetch() {
                return DefaultContainer.this.getInstance(key, data);
            }
        };
    }
}
